﻿/*
 * Created by SharpDevelop.
 * User: Alice
 * Date: 10.03.2012
 * Time: 06:23
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Collections;
using System.Threading;
using System.Timers;

namespace FreewarClone
{
	/// <summary>
	/// Description of Communicator.
	/// </summary>
	public class Communicator
	{
		private TcpClient client;
		private NetworkStream stream;
		public Queue commandQueue;
		private Thread cThread;
		public volatile bool threadStarted;
		public IPAddress clientIP;
		private BugTable bt;
		public DataManager dm;
		private Cron cron;
		
		private void setup()
		{
			this.bt = new BugTable("communicator-telegrams", true);
			this.bt.addColumn("id", typeof(string));
			this.bt.addColumn("timestamp", typeof(DateTime));
			this.bt.addColumn("client-ip", typeof(IPAddress));
			this.bt.addColumn("session-key", typeof(String));
			this.bt.addColumn("telegram", typeof(String));
			this.stream = this.client.GetStream();
			this.commandQueue = new Queue();
			this.clientIP = ((IPEndPoint)this.client.Client.RemoteEndPoint).Address;
			this.threadStarted = true;
			this.cThread = new Thread(this.start);
			this.cron = new Cron();
			this.cron.addJob("checktelegramresponse", new ElapsedEventHandler(this.checkTelegramResponse), 5);
			this.cron.startAllJobs();
			//this.l = new Logger(new Configuration());
			
		}
		
		public Communicator(TcpClient client, DataManager d)
		{
			this.dm = d;
			this.client = client;
			this.setup();
			this.cThread.Start();			
		}
		
		public Communicator(TcpClient client, DataManager d, bool waitForLogin)
		{
			this.dm = d;
			this.client = client;
			this.setup();
			this.cThread = new Thread(this.start);
		}
		
		public void checkTelegramResponse(object sender, ElapsedEventArgs e)
		{
			for(int i=0; i<=this.bt.countRows()-1; i++)
			{
				ArrayList al = this.bt[i];
				DateTime dt = (DateTime)al[1];
				if(dt.AddSeconds(5).CompareTo(DateTime.Now) > 0)
				{
					this.resendTelegram((string)al[4]);
					this.bt.deleteRow(i);
				}
				
			}
		}
		
		public void resendTelegram(string t)
		{
			Telegram tt = Telegram.Parse(t);
			this.sendTelegram(tt);
		}
		
		public void sendTelegram(Telegram t)
		{
			// TODO Gesendete Telegramme müssen in einer Tabelle zwischengespeichert werden
			// damit bei Bedarf das Telegram erneut gesendet wird. Erst wenn das Telegram
			// vom Server bestätigt worden ist wird das Telegramm aus der Tabelle entfernt.
			// Das Telegramm wird solange erneut gesendet bis die Bestätigung beim Client
			// verarbeitet wurde
			// Sollte ein Telegram just in dem Augenblick erneut zum Server gesendet werden
			// wenn die Bestätigung ankommt muss der Server entsprechend reagieren (Telegramm Ignorieren oder so ähnlich)
			string tmp = t.toString();
			if(!t.keyWord.Equals("ACKNOWLEDGE"))
				this.bt.addRow(t.key, DateTime.Now, this.clientIP, "skey", tmp);
			
			this.sendPreparedTelegram(tmp, t.key);
			
		}
		
		/*
		public void sendTelegram(string t)
		{
			string[] arr = t.Split(';');
			Telegram tt = new Telegram((String)arr[0]);
			for(int i=1;i<=arr.Length-1;i++)
			{
				tt.addParameter((String)arr[i]);
			}
			
			this.sendTelegram(tt);
		}
		*/
		
		public void sendPreparedTelegram(String t, string key)
		{
			this.l.writeTelegramToFile(t, false);
			byte[] writeBuffer = Encoding.ASCII.GetBytes(t);
			this.stream.Write(writeBuffer, 0, writeBuffer.Length);
			this.stream.Flush();
		}
		
		public void sendPong()
		{
			Telegram t = new Telegram("PONG", Telegram.REQUEST, this.dm.currentTelegramKey);
			this.sendTelegram(t);
		}
		
		public void sendPing(object sender, ElapsedEventArgs e)
		{
			this.sendTelegram(new Telegram("PING", Telegram.REQUEST, this.dm.currentTelegramKey));
		}
		
		/*
		public void sendChatMessage(String msg, Session s, int time)
		{
			this.sendTelegram("CHATMESSAGE;" + time.ToString() + ";" + s.Player.NickName + ";" + msg);
		}
		*/
		public void sendLogout()
		{
			this.sendTelegram(new Telegram("LOGOUT", Telegram.REQUEST, this.dm.currentTelegramKey));
		}
		
		public void sendSessionData(Session s)
		{
			Telegram t = new Telegram("SESSIONDATA", Telegram.REQUEST, this.dm.currentTelegramKey);
			t.addParameter(s.key);
			t.addParameter(s.Player.ID.ToString());
			t.addParameter(s.Player.NickName);
			t.addParameter(s.com.clientIP.ToString());
			t.addParameter(s.Player.bannedUntil.ToString());
			t.addParameter(s.Player.countWrongPassword.ToString());
			/*
			String t = "SESSIONDATA;";
			t = t + s.key + ";";
			t = t + s.Player.ID + ";";
			t = t + s.Player.NickName + ";";
			t = t + s.com.clientIP.ToString() + ";";
			t = t + s.Player.bannedUntil.ToString() + ";";
			t = t + s.Player.countWrongPassword.ToString();
			*/
			this.sendTelegram(t);
		}
		
		public void sendEmptySessionData()
		{
			this.sendTelegram(new Telegram("SESSIONDATA", Telegram.REQUEST, this.dm.currentTelegramKey));
		}
		
		public void sendUserData(User u)
		{
			Telegram t = new Telegram("USERDATA", Telegram.REQUEST, this.dm.currentTelegramKey);
			t.addParameter(u.NickName);
			t.addParameter(u.Password);
			
			//string tmp = "USERDATA;" + u.ID + ";" + u.NickName + ";" + u.Password;
			this.sendTelegram(t);
		}
		
		public void sendLoginOk()
		{
			Telegram t = new Telegram("LOGIN", Telegram.RESPONSE, this.dm.currentTelegramKey);
			t.addParameter("OK");
			this.sendTelegram(t);
		}
		
		/*
		public void sendFields(ArrayList f)
		{
			for(int i=0; i<= f.Count-1; i++)
			{
				Hashtable h = (Hashtable)f[i];
				this.sendTelegram("FIELD;"+((int)h["counter"]).ToString()+";"+h["x"]+";"+h["y"]+";"+h["name"]+";"+h["description"]+";");
			}
		}
		*/
		
		/*
		public void sendError(Error e)
		{
			Telegram t = new Telegram("ERROR"
			this.sendTelegram("ERROR;"+e.toString(";"));
		}
		*/
		public void closeConnection()
		{
			this.threadStarted = false;
			this.stream.Close();
			this.client.Close();
		}
		
	
		
		public void start()
		{
			String commandLine = null;
			String numBytes = null;
			byte[] tmpbyte = new Byte[1];
			byte b = byte.Parse("59");
			
			while(this.threadStarted)
			{
				if(stream.DataAvailable)
				{
					commandLine = null;
					
					tmpbyte = new Byte[1];
					while(!tmpbyte[0].Equals(b))
					{
						// TODO Vorkehrungen treffen dass im Falle eines Falschen Telegrams (falsche oder fehlende byteanzahl keine endlos-schleife entsteht)
						stream.Read(tmpbyte, 0, tmpbyte.Length);
						numBytes += Encoding.ASCII.GetString(tmpbyte);
					}
					
					int i = Int16.Parse(numBytes.Substring(0,numBytes.Length-1));
					
					for(int j=0;j<=i-1;j++)
					{
						stream.Read(tmpbyte, 0, tmpbyte.Length);
						commandLine += Encoding.ASCII.GetString(tmpbyte);
					}
					
					this.l.writeTelegramToFile(numBytes+commandLine, true);
					Telegram t = Telegram.Parse(numBytes+commandLine);
					if(!t.keyWord.Equals("ACKNOWLEDGE"))
					{
						// Bestätigung des Empfangs des Telegrams versenden (aber nur wenn das empfange Telegramm kein ACKNOWLEDGE ist
						Telegram tt = new Telegram(t, this.dm.currentTelegramKey);
						this.sendTelegram(tt);
						this.commandQueue.Enqueue(t);
					} else {
						this.bt.deleteRow("id", t[0]);
					}
					numBytes = null;				
				}
			}
		}
	}
}
